package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.product.api.domain.Product;
import com.spzx.product.api.domain.ProductDetails;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPrice;
import com.spzx.product.api.domain.vo.SkuQuery;
import com.spzx.product.api.domain.vo.SkuStockVo;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.xml.stream.events.EndDocument;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品Service业务层处理
 */
@Slf4j
@Service
@Transactional
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private ProductDetailsMapper productDetailsMapper;

    @Autowired
    private SkuStockMapper skuStockMapper;

    //@Autowired
    //private StringRedisTemplate stringRedisTemplate; //适合 key和value都是字符串类型

    @Autowired
    private RedisTemplate redisTemplate; //适合值是任意类型

    /**
     * 查询商品列表
     *
     * @param product 商品
     * @return 商品
     */
    @Override
    public List<Product> selectProductList(Product product) {
        return productMapper.selectProductList(product);
    }

    //原子性
    @Override
    public int insertProduct(Product product) {
        //1.保存Product对象到product表
        productMapper.insert(product); //主键回填

        //2.保存List<ProductSku>对象到product_sku表
        List<ProductSku> productSkuList = product.getProductSkuList();
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        int size = productSkuList.size();
        for (int i = 0; i < size; i++) {
            ProductSku productSku = productSkuList.get(i);
            productSku.setSkuCode(product.getId() + "_" + i);
            productSku.setSkuName(product.getName() + " " + productSku.getSkuSpec());
            productSku.setProductId(product.getId());
            productSkuMapper.insert(productSku);

            //添加商品库存  //3.保存List<SkuStock>对象到sku_stock表
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStockMapper.insert(skuStock);
        }

        //4.保存ProductDetails对象到product_details表
        ProductDetails productDetails = new ProductDetails();
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetails.setProductId(product.getId());
        productDetailsMapper.insert(productDetails);

        return 1;
    }


    @Override
    public Product selectProductById(Long id) {
        //1.根据id查询Product对象
        Product product = productMapper.selectById(id);

        //2.封装扩展字段：查询商品对应多个List<ProductSku>
        //select * from product_sku where product_id =?
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));
        List<Long> productSkuIdList = productSkuList.stream().map(productSku -> productSku.getId()).toList();


        // select * from sku_stock where sku_id in (1,2,3,4,5,6)
        List<SkuStock> skuStockList = skuStockMapper.selectList(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        Map<Long, Integer> skuIdToTatalNumMap = skuStockList.stream().collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));
        productSkuList.forEach(productSku -> {
            //返回ProductSku对象，携带了库存数据；
            productSku.setStockNum(skuIdToTatalNumMap.get(productSku.getId()));
        });

        product.setProductSkuList(productSkuList);

        //3.封装扩展字段：商品详情图片List<String>
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
        String imageUrls = productDetails.getImageUrls();   //url,url,url
        String[] urls = imageUrls.split(",");
        product.setDetailsImageUrlList(Arrays.asList(urls));
        //返回Product对象
        return product;
    }


    @Override
    public int updateProduct(Product product) {
        //延时双删 保证 数据一致性，最终一致性
        //修改数据库数据之前，先删除一遍缓存数据
        List<ProductSku> productSkuList = product.getProductSkuList();
        List<Long> skuIdList = productSkuList.stream().map(ProductSku::getId).toList();

        for (Long skuId : skuIdList) {
            String datakey = "product:sku:" + skuId;
            redisTemplate.delete(datakey);
        }

        //1.更新Product
        productMapper.updateById(product);

        //2.更新SKU   List<ProductSku>
        if (CollectionUtils.isEmpty(productSkuList)) {
            throw new ServiceException("SKU数据为空");
        }
        productSkuList.forEach(productSku -> {
            productSkuMapper.updateById(productSku);

            //3.更新库存   List<ProductSku> -> 获取扩展字段stockNum
            SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setAvailableNum(skuStock.getTotalNum() - skuStock.getLockNum());
            skuStockMapper.updateById(skuStock);
        });

        //4.更新详情ProductDetails
        ProductDetails productDetails = productDetailsMapper
                .selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, product.getId()));
        productDetails.setImageUrls(String.join(",", product.getDetailsImageUrlList()));
        productDetailsMapper.updateById(productDetails);

        // 修改数据库后，睡一会再删除一遍缓存
        try {
            Thread.sleep(500);  // 保证数据库端 主从复制 可以全部完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (Long skuId : skuIdList) {
            String dataKey = "product:sku" + skuId;
            redisTemplate.delete(dataKey);
        }

        return 1;
    }


    @Override
    public int deleteProductByIds(Long[] ids) {
        //1.删除Product表数据
        // delete from product where id in (1,2)
        productMapper.deleteBatchIds(Arrays.asList(ids));

        //2.删除ProductSku表数据
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, Arrays.asList(ids)));
        List<Long> productSkuIdList = productSkuList.stream().map(ProductSku::getId).toList();
        productSkuMapper.deleteBatchIds(productSkuIdList);

        //3.删除SkuStock表数据
        skuStockMapper.delete(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, productSkuIdList));

        //4.删除ProductDetails表数据
        // delete from product_details where product_id in (1,2)
        productDetailsMapper.delete(new LambdaQueryWrapper<ProductDetails>().in(ProductDetails::getProductId, Arrays.asList(ids)));
        return 1;
    }


    @Override
    public void updateAuditStatus(Long id, Integer auditStatus) {
        Product product = new Product();
        product.setId(id);
        if (auditStatus == 1) {
            product.setAuditStatus(1);
            product.setAuditMessage("审批通过");
        } else {
            product.setAuditStatus(-1);
            product.setAuditMessage("审批拒绝");
        }
        productMapper.updateById(product);
    }

    @Transactional
    @Override
    public void updateStatus(Long id, Integer status) {
        Product product = new Product();
        product.setId(id);
        if (status == 1) {
            product.setStatus(1);  // spu 上架了，那么对应的多个sku 也需要上架
            ProductSku productSkuUpdate = new ProductSku();
//            productSkuUpdate.setProductId(id);
            productSkuUpdate.setStatus(1);
            productSkuMapper.update(productSkuUpdate, new LambdaUpdateWrapper<ProductSku>().eq(ProductSku::getProductId, id));
            //新上架的 sku 也需要存放放到 bitmap 中
            List<ProductSku> skuList = productSkuMapper
                    .selectList(new LambdaQueryWrapper<ProductSku>()
                            .eq(ProductSku::getProductId, id)
                            .eq(ProductSku::getStatus, 1));
            if (!CollectionUtils.isEmpty(skuList)) {
                String key = "sku:product:data";
                for (ProductSku productSku : skuList) {
                    Long skuId = productSku.getId();
                    redisTemplate.opsForValue().setBit(key, skuId, true);  // true 表示 1 ， false 表示 0
                }
            }
        } else {

            List<ProductSku> skuList = productSkuMapper
                    .selectList(new LambdaQueryWrapper<ProductSku>()
                            .eq(ProductSku::getProductId, id)
                            .eq(ProductSku::getStatus, 1));
            if (!CollectionUtils.isEmpty(skuList)) {
                String key = "sku:product:data";
                for (ProductSku productSku : skuList) {
                    Long skuId = productSku.getId();
//                    redisTemplate.delete(key); // 当前商品下架了，其他商品还没有下架，不能删除 key
                    redisTemplate.opsForValue().setBit(key, skuId, false);
                    String dataKey = "product:sku:" + skuId;  // 注意key 名称一致
                    redisTemplate.delete(dataKey);
                }
            }
            product.setStatus(-1);
            ProductSku productSkuUpdate = new ProductSku();
            productSkuUpdate.setStatus(-1);
            productSkuMapper.update(productSkuUpdate, new LambdaUpdateWrapper<ProductSku>().eq(ProductSku::getProductId, id));
        }
        productMapper.updateById(product);
    }


    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.getTopSale();
    }


    @Override
    public List<ProductSku> skuList(SkuQuery skuQuery) {
        return productSkuMapper.skuList(skuQuery);
    }


    /**
     * 服务提供者：6个接口来服务于商品详情查询。需要进行优化，提供查询效率。
     * 需要使用redis来提高性能。
     * <p>
     * 先从缓存中获取数据，缓存中存在直接返回，缓存中不存在，获取分布式锁（避免缓存击穿），从数据库获取，然后，存放到缓存中，下次利用
     * 注意:
     * 查询 null 也要存放到缓存中（一定程度上缓解缓存穿透），缓存时间比正常数据短一些，失效时间随即增加1-5分钟，避免缓存雪崩
     * 增加 bitmap 或布隆过滤器，彻底解决缓存穿透问题
     */

    // 根据SkuID查询SKU商品信息
    @Override
    public ProductSku getProductSku(Long skuId) {

        try {
            //1. 优先从缓存中获取数据
            //1.1 构建业务数据 key 形式：前缀+业务唯一标识
            String datakey = "product:sku" + skuId;

            //1.2 查询 redis 获取业务数据
            ProductSku productSku = (ProductSku) redisTemplate.opsForValue().get(datakey);

            //1.3 命中缓存则直接返回
            if (productSku != null) {
                log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return productSku;
            }

            //2. 尝试获取分布式锁（set k v ex nx可能获取锁失败）
            //2.1 构建锁 key
            String lockKey = "product:sku:lock" + skuId;

            //2.2 采用 UUID 作为线程标识，枷锁标记，续期、释放锁都需要这个标记
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");

            //2.3 利用 Redis 提供 set nx ex 获取分布式锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

            if (flag) {
                log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                try {
                    //3.获取锁成功执行业务-从数据库中获取，将查询业务数据存入缓存 Redis
                    productSku = this.getProductSkuFromDB(skuId);
                    // 设置失效时间  null -> 60  其他 -> 600
                    long ttl = productSku == null ? 1 * 60 : 10 * 60;
                    //将数据放入缓存中
                    redisTemplate.opsForValue().set(datakey, productSku, ttl, TimeUnit.SECONDS);

                    return productSku;
                } finally {
                    //4. 业务执行完毕 释放锁   LUA 脚本保证原子性 ，释放自己的锁，避免释放别人的锁
                    String scripText = "if redis.call('get',KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "return redis.call('del',KEYS[1])\n" +
                            "else\n" +
                            "return 0\n" +
                            "end";

                    //创建执行redis脚本对象
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    // 将 Lua 脚本的文本内容设置到 redisScript 对象中
                    redisScript.setScriptText(scripText);
                    //返回结果类型
                    redisScript.setResultType(Long.class);

                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
                }
            } else {
                try {
                    //5. 获取锁失败则自旋（业务要求必须执行）
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.error("获取锁失败，自选：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                return this.getProductSku(skuId);
            }
        } catch (Exception e) {
            // 兜底处理方案：Redis 服务有问题，将业务数据获取自动从数据库获取
            log.error("[商品服务]查询商品信息异常：{}", e);
            return this.getProductSkuFromDB(skuId);
        }
    }

    // 从数据库中获取
    public ProductSku getProductSkuFromDB(Long skuId) {
        return productSkuMapper.selectById(skuId);
    }

    @Override
    public Product getProduct(Long id) {
        return productMapper.selectById(id);
    }


    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        ProductSku productSku = productSkuMapper.selectOne(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getId, skuId).select(ProductSku::getSalePrice, ProductSku::getMarketPrice));
        SkuPrice skuPrice = new SkuPrice();
        BeanUtils.copyProperties(productSku, skuPrice);
        return skuPrice;
    }


    @Override
    public ProductDetails getProductDetails(Long id) {
        return productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, id));
    }


    @Override
    public Map<String, Long> getSkuSpecValue(Long id) {
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id).select(ProductSku::getId, ProductSku::getSkuSpec));
        Map<String, Long> skuSpecValueMap = new HashMap<>();
        productSkuList.forEach(item -> {
            skuSpecValueMap.put(item.getSkuSpec(), item.getId());
        });
        return skuSpecValueMap;
    }


    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuId));
        SkuStockVo skuStockVo = new SkuStockVo();
        BeanUtils.copyProperties(skuStock, skuStockVo);
        return skuStockVo;
    }


    // select * from product_sku where id in (1,2,3)
    // select id,sale_price,market_price from product_sku where id in (1,2,3)
    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        if (CollectionUtils.isEmpty(skuIdList)) {
            return new ArrayList<SkuPrice>();
        }
        List<ProductSku> skuList = productSkuMapper
                .selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIdList)
                        .select(ProductSku::getId, ProductSku::getSalePrice, ProductSku::getMarketPrice));
        if (CollectionUtils.isEmpty(skuList)) {
            return new ArrayList<SkuPrice>();
        }
        return skuList.stream().map((sku) -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(sku.getId());
            skuPrice.setSalePrice(sku.getSalePrice());
            skuPrice.setMarketPrice(sku.getMarketPrice());
            return skuPrice;
        }).toList();
    }


    //-----------------------------------------------------------------/

    /**
     * 作业： 检查与锁定库存 能否能否一步完成
     * update sku_stock
     * set available_num = available_num - 5, lock_num = lock_num + 5
     * where id = 20 and available_num > 5
     *
     * @param orderNo       订单号
     * @param skuLockVoList 需要被锁定的商品数量信息
     * @return
     */

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        // OpenFeign 远程调用，可能会进行重试，需要去重处理（使用 redis setnx 设置共享标记 分布式锁）
        String key = "sku:checkAndLock:" + orderNo;  // 分布式锁的key
        String dataKey = "sku:lock:data:" + orderNo;  // 业务完成，需要将锁定的数据保存到 redis 中时用到的 key

        // 设置分布式锁 ——> redis 中设置一个键值对  键不存在 设置成功 返回true , 否则返回 false
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!ifAbsent) {  // 没拿到锁标记，说明当前锁定业务已完成，那么，1个小时内进行重试，不允许。当前线程正执行检查锁定库存业务，另一个线程过来重试，不允许。
            // 检查 datakey 是否存在
            if (redisTemplate.hasKey(dataKey)) {   // 说明业务已经完成，不能重试，直接返回成功
                return "";
            } else {  // 不存在
                return "正在锁定库存，无法重试";
            }
        }

        // 1.检查库存够不够     传入的 sku 锁定信息列表是否为空
        if (CollectionUtils.isEmpty(skuLockVoList)) {
            return "参数为空";
        }

        StringBuilder builder = new StringBuilder("");
        // 遍历 sku 锁定信息列表
        for (SkuLockVo skuLockVo : skuLockVoList) {
            // 调用Mapper方法检查指定商品的库存情况，并尝试锁定库存
            SkuStock skuStock = skuStockMapper.check(skuLockVo.getSkuId(), skuLockVo.getSkuNum());  // for update 增加数据库行锁，不允许其他业务来动锁定的数据
            if (skuStock == null) {  // 检查结果为空 -> 商品库存不足 -> 将返回信息追加到 builder
                builder.append("商品【" + skuLockVo.getSkuId() + "】库存不够；");
            }
        }

        // 2.不够 锁定库存失败
        if (StringUtils.hasText(builder.toString())) {  // 说明检查库存失败，存在商品库存不够的情况
            redisTemplate.delete(key);  //   删除分布式锁，这样openfeign 才可以进行重试
            return builder.toString(); // 分布式锁不解除。 正常返回字符串，事务正常提交，for update 行数自动释放
        }

        // 3.库存够用，锁定库存成功
        for (SkuLockVo skuLockVo : skuLockVoList) {
            int count = skuStockMapper.lock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
            if (count == 0) {  // count 表示 sql 语句起作用的行数，为0表示锁定库存失败
                redisTemplate.delete(key);
                throw new ServiceException("锁定库存失败"); // 事务回滚
            }
        }

        // 4.把锁定数据保存到 redis 缓存中
        // 不设置过期时间：原因，由其他的业务来删除缓存
        // 用户手动取消订单或延迟消息 15 分钟处理未支付自动取消订单 --->  需要解锁库存  删除缓存
        // 用户 15 分钟支付订单 --->  需要减库存  删除缓存

        redisTemplate.opsForValue().set(dataKey,skuLockVoList);  // 需要设置过期时间吗？  不需要

//        redisTemplate.delete(key); //  不删除，继续 VB　保留，继续解决重试问题


        return "";  //空就表示 成功结果
    }

}